﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;

namespace NNImage
{
    public partial class FormGlowny : Form
    {
        #region Pola prywatne
        private Stream _plik = null;
        private Bitmap _bmIn = null;
        private Bitmap _bmOut = null;
        private Podglad _podglad = null;
        private Wynik _wynik = null;
        private string[] _metodyKompresji = { "WTA (brak sąsiedztwa)", "S. prostokątne", "S. gaussowskie" };
        private string[] _funkcjeOdleglosci = { "Miara euklidesowa" };
        private IFunkcjaSasiedztwa _funkcjaSasiedztwa = null;
        private MiaraEuklidesowa _miaraOdleglosci = null;
        private KohonenMap _siecKohonena = null;
        private List<Point> _slownikNeuronow = new List<Point>();
        #endregion

        #region Konstruktor
        /// <summary>
        /// Inicjalizuje klase glownego okna i ustawia odpowiednie
        /// wartosci poczatkowe
        /// </summary>
        public FormGlowny()
        {
            InitializeComponent();
            menuStripMenuGlowne.Enabled = true;
            groupBoxRaport.Enabled = false;
            groupBoxInformacje.Enabled = false;
            groupBoxUstawienia.Enabled = false;
            buttonKompresja.Enabled = false;
            buttonPodglad.Enabled = false;
            buttonZapis.Enabled = false;
            checkBoxPodglad.Enabled = false;
            comboBoxMetoda.DataSource = _metodyKompresji;
            comboBoxMiaraOdleglosci.DataSource = _funkcjeOdleglosci;

        }
        #endregion

        #region Eventy GUI
        /// <summary>
        /// Metoda odpalana po wybraniu pozycji 'Kompresja' z menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void kompresjaToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialogOtworzPlik.Title = "Otwórz plik do kompresji";
            openFileDialogOtworzPlik.InitialDirectory = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            openFileDialogOtworzPlik.Filter = "Bitmapy (*.bmp)|*.bmp";
            if (openFileDialogOtworzPlik.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    if ((_plik = openFileDialogOtworzPlik.OpenFile()) != null)
                    {

                        try
                        {
                            _bmIn = (Bitmap)Bitmap.FromStream(_plik);
                            ResetUstawien();
                            groupBoxUstawienia.Enabled = true;
                            groupBoxInformacje.Enabled = true;
                            groupBoxRaport.Enabled = false;
                            buttonKompresja.Enabled = true;
                            buttonPodglad.Enabled = true;
                            buttonAnuluj.Enabled = false;
                            buttonZapis.Enabled = false;
                            checkBoxPodglad.Enabled = true;
                            textBoxNazwaPliku.Text = String.Format("{0}", System.IO.Path.GetFileName(openFileDialogOtworzPlik.FileName));
                            labelSzerokoscValue.Text = String.Format("{0} px", _bmIn.Width);
                            labelWysokoscValue.Text = String.Format("{0} px", _bmIn.Height);
                            labelRozmiarValue.Text = String.Format("{0} b", _plik.Length);
                            labelRozmiarPoValue.Text = string.Empty;
                            labelRamkiValue.Text = string.Empty;
                            labelStopienKompresjiValue.Text = string.Empty;
                            labelWspolczynnikKompresjiValue.Text = string.Empty;
                            labelPSNRValue.Text = string.Empty;
                            numericUpDownIteracje.Maximum = Convert.ToInt32((_bmIn.Width / numericUpDownRozmiarRamki.Value) * (_bmIn.Height / numericUpDownRozmiarRamki.Value));
                            numericUpDownIteracje.Value = Convert.ToInt32((_bmIn.Width / numericUpDownRozmiarRamki.Value) * (_bmIn.Height / numericUpDownRozmiarRamki.Value));

                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Błąd! Niepoprawny format. Kod błędu: " + ex.Message, "Błąd!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }


                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Błąd! Nie można odczytać pliku. Kod błędu: " + ex.Message, "Błąd!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// Metoda odpalana po wybraniu pozycji 'Wyjście' z menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void wyjścieToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Dispose();
        }

        /// <summary>
        /// Metoda odpalana przy kazdej zmianie wielkosci ramki.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void numericUpDownRozmiarRamki_ValueChanged(object sender, EventArgs e)
        {
            labelRozmiarRamki2.Text = String.Format("{0}px x {0}px", numericUpDownRozmiarRamki.Value);
            numericUpDownIteracje.Maximum = Convert.ToInt32((_bmIn.Width / numericUpDownRozmiarRamki.Value) * (_bmIn.Height / numericUpDownRozmiarRamki.Value));
            numericUpDownIteracje.Value = Convert.ToInt32((_bmIn.Width / numericUpDownRozmiarRamki.Value) * (_bmIn.Height / numericUpDownRozmiarRamki.Value));
        }

        /// <summary>
        /// Metoda odpalana przy kazdej zmianie ilosci neuronow.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void numericUpDownNeurony_ValueChanged(object sender, EventArgs e)
        {
            labelNeurony2.Text = String.Format("{0} neuronów", numericUpDownNeurony.Value * numericUpDownNeurony.Value);
        }

        /// <summary>
        /// Metoda odpalana po nacisnieciu przycisku podglad,
        /// otwiera nowe okno z podgladem przetwarzanego obrazu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonPodglad_Click(object sender, EventArgs e)
        {
            if (_podglad == null || _podglad.IsDisposed)
            {
                _podglad = new Podglad(_bmIn, textBoxNazwaPliku.Text);
                _podglad.Show();
            }
        }

        /// <summary>
        /// Metoda odpalana po nacisnieciu przycisku 'Kompresja'.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonKompresja_Click(object sender, EventArgs e)
        {
            groupBoxInformacje.Enabled = false;
            groupBoxRaport.Enabled = false;
            groupBoxUstawienia.Enabled = false;
            menuStripMenuGlowne.Enabled = false;
            checkBoxPodglad.Enabled = false;
            buttonKompresja.Enabled = false;
            buttonZapis.Enabled = false;
            buttonPodglad.Enabled = false;
            numericUpDownPromien.Enabled = false;
            buttonAnuluj.Enabled = true;
            progressBarPostep.Value = 0;
            progressBarPostep.Step = 1;
            progressBarPostep.Maximum = Convert.ToInt32(numericUpDownIteracje.Value+_bmIn.Height*_bmIn.Width);
            
            if (richTextBoxStatus.Text != string.Empty)
                richTextBoxStatus.Text = richTextBoxStatus.Text.Remove(0);
            _slownikNeuronow.RemoveRange(0, _slownikNeuronow.Count);

            switch (comboBoxMiaraOdleglosci.SelectedIndex)
            {
                case 0:
                    _miaraOdleglosci = new MiaraEuklidesowa();
                    break;
                default:
                    _miaraOdleglosci = new MiaraEuklidesowa();
                    break;
            }


            switch (comboBoxMetoda.SelectedIndex)
            {
                case 0:
                    _funkcjaSasiedztwa = new SasiedztwoWTA();
                    break;
                case 1:
                    _funkcjaSasiedztwa = new SadziedztwoProstokatne();
                    break;
                case 2:
                    _funkcjaSasiedztwa = new SasiedztwoGaussowskie();
                    break;
                default:
                    _funkcjaSasiedztwa = new SasiedztwoWTA();
                    break;
            }

            _siecKohonena = new KohonenMap(
                        Convert.ToInt32(numericUpDownNeurony.Value),
                        Convert.ToInt32(numericUpDownRozmiarRamki.Value * numericUpDownRozmiarRamki.Value),
                        _miaraOdleglosci,
                        _funkcjaSasiedztwa,
                        Convert.ToDouble(numericUpDownAlfa.Value),
                         Convert.ToDouble(numericUpDownPotencjal.Value),
                          Convert.ToInt32(numericUpDownPromien.Value)
                     );

            if (!backgroundWorker.IsBusy)
                backgroundWorker.RunWorkerAsync();
        }


        /// <summary>
        /// Metoda odpalana po nacisnieciu przycisku 'Zapisz'.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonZapis_Click(object sender, EventArgs e)
        {
            saveFileDialogZapiszPlik.Title = "Zapisz";
            saveFileDialogZapiszPlik.InitialDirectory = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            saveFileDialogZapiszPlik.Filter = "Bitmapy (*.bmp)|*.bmp";
            if (saveFileDialogZapiszPlik.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    _bmOut.Save(saveFileDialogZapiszPlik.FileName, ImageFormat.Bmp);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Błąd! Nie można zapisać pliku. Kod błędu: " + ex.Message, "Błąd!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }

        }

        /// <summary>
        /// Metoda odpalana po nacisnieciu przycisku 'Anuluj',
        /// przerywa operacje kompresji lub dekompresji.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonAnuluj_Click(object sender, EventArgs e)
        {
            if (backgroundWorker.WorkerSupportsCancellation)
            {
                backgroundWorker.CancelAsync();
                buttonAnuluj.Enabled = false;
                progressBarPostep.Value = 0;
            }
        }

        /// <summary>
        /// Metoda odpalana przy zmianie metody kompresji.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void comboBoxMetoda_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (comboBoxMetoda.SelectedIndex == 0)
            {
                numericUpDownPromien.Enabled = false;
            }
            else
            {
                numericUpDownPromien.Enabled = true;
            }
        }

        /// <summary>
        /// Metoda wyświetlająca messagebox z informacjami o programie.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void oProgramieToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Aplikacja do kompresji obrazów za pomocą\nsieci Kohonena.\n\nAutorzy: Marek Kowalczyk, Maciej Bartoszek\n\nProwadzący: dr inż. Krzysztof Cyran\n\nCopyright \u00a9 2011", "NNImage", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        #endregion

        #region Eventy BackgroundWorkera
        /// <summary>
        /// Metoda glowna BackgroundWorkera, w niej dokonywana jest kompresja.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            int progres = 0;
            int aktualnaIteracja = 0;
            int iteracje = Convert.ToInt32(numericUpDownIteracje.Value);
            int rozmiarRamki = Convert.ToInt32(numericUpDownRozmiarRamki.Value);
            _bmOut = new Bitmap(_bmIn.Width, _bmIn.Height);
            Neuron zwyciezca;
            this.Invoke(new MethodInvoker(delegate
            {
                richTextBoxStatus.AppendText("Przetwarzanie ramek...\n");
            }));

            try
            {
                _siecKohonena.WczytajWejscia(_bmIn);
                this.Invoke(new MethodInvoker(delegate
                {
                    richTextBoxStatus.AppendText("Uczenie sieci...\n");
                }));

                for (int i = 0; i < iteracje; ++i)
                {

                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        return;
                    }

                    _siecKohonena.KorygujWagi(_siecKohonena.wektoryWejscioweRandom[i], aktualnaIteracja);
                    progres++;
                    worker.ReportProgress(progres);
                    aktualnaIteracja++;
                }



                int kolor = 0;
                this.Invoke(new MethodInvoker(delegate
                {
                    richTextBoxStatus.AppendText("Generowanie bitmapy wynikowej...\n");
                }));
                for (int i = 0; i < _bmOut.Height / rozmiarRamki; ++i)
                {
                    for (int j = 0; j < _bmOut.Width / rozmiarRamki; ++j)
                    {
                        zwyciezca = _siecKohonena.ZnajdzZwyciezce(_siecKohonena.wektoryWejsciowe[i * (_bmOut.Width / rozmiarRamki) + j]);
                        for (int k = 0; k < rozmiarRamki; ++k)
                        {
                            for (int l = 0; l < rozmiarRamki; ++l)
                            {

                                if (worker.CancellationPending)
                                {
                                    e.Cancel = true;
                                    return;
                                }

                                kolor = zwyciezca.Wagi[k * rozmiarRamki + l];
                                _bmOut.SetPixel(l + j * rozmiarRamki,
                                    k + i * rozmiarRamki,
                                    Color.FromArgb(kolor, kolor, kolor)
                                    );


                                progres++;
                                worker.ReportProgress(progres);
                            }
                        }
                        if (!_slownikNeuronow.Contains(zwyciezca.Koordynaty))
                        {
                            _slownikNeuronow.Add(zwyciezca.Koordynaty);
                        }
                    }
                }

                _wynik = new Wynik(_bmOut);

            }
            catch (Exception ex)
            {
                e.Cancel = true;
                this.Invoke(new MethodInvoker(delegate
                {
                    MessageBox.Show(this, ex.Message, "Błąd!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }));
                return;
            }
        }

        /// <summary>
        /// Metoda odpalana przy raportowaniu postepow, 
        /// odpowiada za przesuwanie progress bara.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBarPostep.PerformStep();
        }

        /// <summary>
        /// Metoda odpalana po zakonczeniu pracy przez BackgroundWorkera.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            buttonAnuluj.Enabled = false;
            menuStripMenuGlowne.Enabled = true;
            progressBarPostep.Value = 0;
            if (!e.Cancelled)
            {
                if (checkBoxPodglad.Checked)
                    _wynik.Show();
                buttonZapis.Enabled = true;
                groupBoxRaport.Enabled = true;
                GenerujRaport();
                richTextBoxStatus.AppendText("Gotowe!\n");
            }
            else
            {
                richTextBoxStatus.AppendText("Anulowano!\n");
                labelRozmiarPoValue.Text = string.Empty;
                labelRamkiValue.Text = string.Empty;
                labelStopienKompresjiValue.Text = string.Empty;
                labelWspolczynnikKompresjiValue.Text = string.Empty;
                labelPSNRValue.Text = string.Empty;
                groupBoxInformacje.Enabled = true;
                groupBoxUstawienia.Enabled = true;
                buttonKompresja.Enabled = true;
                buttonPodglad.Enabled = true;
                checkBoxPodglad.Enabled = true;
            }
        }
        #endregion

        #region Metody
        /// <summary>
        /// Generuje raport po kompresji i wyświetla go w oknie aplikacji.
        /// </summary>
        public void GenerujRaport()
        {
            double MSE = 0;
            double PSNR = 0;
            int ramki = _siecKohonena.IleRamek;
            int ramka = _siecKohonena.RozmiarWejscia;
            int rozmiarPo = ramki + _slownikNeuronow.Count * ramka * 8;
            int neurony = _siecKohonena.warstwaWyjsciowa.Length;
            double wspolczynnikKompresji = (ramki * ramka * 8) / (ramki * Math.Log(neurony) + neurony * ramka * 8);
            for (int i = 0; i < _bmOut.Height; ++i)
                for (int j = 0; j < _bmOut.Width; ++j)
                {
                    MSE += Math.Pow((_bmIn.GetPixel(j, i).R + _bmIn.GetPixel(j, i).G + _bmIn.GetPixel(j, i).B) / 3 - _bmOut.GetPixel(j, i).R, 2);
                }
            MSE /= (_bmOut.Width * _bmOut.Height);
            PSNR = 10 * Math.Log10(255 * 255 / MSE);
            this.Invoke(new MethodInvoker(delegate
                    {
                        labelRamkiValue.Text = String.Format("{0}", ramki);
                        labelRamkiValue.Visible = true;

                        labelRozmiarPoValue.Text = String.Format("{0} b", rozmiarPo);
                        labelRozmiarPoValue.Visible = true;

                        labelStopienKompresjiValue.Text = String.Format("{0} %", Convert.ToInt32((Convert.ToDouble(_plik.Length - rozmiarPo) / _plik.Length) * 100));
                        labelStopienKompresjiValue.Visible = true;

                        labelWspolczynnikKompresjiValue.Text = String.Format("{0:F}", wspolczynnikKompresji);
                        labelWspolczynnikKompresjiValue.Visible = true;

                        labelPSNRValue.Text = String.Format("{0:F} dB", PSNR);
                        labelPSNRValue.Visible = true;
                    }));


        }

        /// <summary>
        /// Metoda przywraca domyślne wartości w ustawieniach.
        /// </summary>
        public void ResetUstawien()
        {
            comboBoxMetoda.SelectedIndex = 0;
            comboBoxMiaraOdleglosci.SelectedIndex = 0;
            numericUpDownAlfa.Value = Convert.ToDecimal(0.7);
            numericUpDownIteracje.Value = 1;
            numericUpDownNeurony.Value = 16;
            numericUpDownPotencjal.Value = Convert.ToDecimal(0.75);
            numericUpDownRozmiarRamki.Value = 4;
            numericUpDownPromien.Value = 1;
            if (richTextBoxStatus.Text != string.Empty)
                richTextBoxStatus.Text = richTextBoxStatus.Text.Remove(0);
            _slownikNeuronow.RemoveRange(0, _slownikNeuronow.Count);
        }
        #endregion
    }
}
